// noinspection ES6ConvertVarToLetConst

//  json2.js
//  2017-06-12
//  Public Domain.
//  NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.

//  USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
//  NOT CONTROL.

//  This file creates a global JSON object containing two methods: stringify
//  and parse. This file provides the ES5 JSON capability to ES3 systems.
//  If a project might run on IE8 or earlier, then this file should be included.
//  This file does nothing on ES5 systems.

//      JSON.stringify(value, replacer, space)
//          value       any JavaScript value, usually an object or array.
//          replacer    an optional parameter that determines how object
//                      values are stringified for objects. It can be a
//                      function or an array of strings.
//          space       an optional parameter that specifies the indentation
//                      of nested structures. If it is omitted, the text will
//                      be packed without extra whitespace. If it is a number,
//                      it will specify the number of spaces to indent at each
//                      level. If it is a string (such as "\t" or "&nbsp;"),
//                      it contains the characters used to indent at each level.
//          This method produces a JSON text from a JavaScript value.
//          When an object value is found, if the object contains a toJSON
//          method, its toJSON method will be called and the result will be
//          stringified. A toJSON method does not serialize: it returns the
//          value represented by the name/value pair that should be serialized,
//          or undefined if nothing should be serialized. The toJSON method
//          will be passed the key associated with the value, and this will be
//          bound to the value.

//          For example, this would serialize Dates as ISO strings.

//              Date.prototype.toJSON = function (key) {
//                  function f(n) {
//                      // Format integers to have at least two digits.
//                      return (n < 10)
//                          ? "0" + n
//                          : n;
//                  }
//                  return this.getUTCFullYear()   + "-" +
//                       f(this.getUTCMonth() + 1) + "-" +
//                       f(this.getUTCDate())      + "T" +
//                       f(this.getUTCHours())     + ":" +
//                       f(this.getUTCMinutes())   + ":" +
//                       f(this.getUTCSeconds())   + "Z";
//              };

//          You can provide an optional replacer method. It will be passed the
//          key and value of each member, with this bound to the containing
//          object. The value that is returned from your method will be
//          serialized. If your method returns undefined, then the member will
//          be excluded from the serialization.

//          If the replacer parameter is an array of strings, then it will be
//          used to select the members to be serialized. It filters the results
//          such that only members with keys listed in the replacer array are
//          stringified.

//          Values that do not have JSON representations, such as undefined or
//          functions, will not be serialized. Such values in objects will be
//          dropped; in arrays they will be replaced with null. You can use
//          a replacer function to replace those with JSON values.

//          JSON.stringify(undefined) returns undefined.

//          The optional space parameter produces a stringification of the
//          value that is filled with line breaks and indentation to make it
//          easier to read.

//          If the space parameter is a non-empty string, then that string will
//          be used for indentation. If the space parameter is a number, then
//          the indentation will be that many spaces.

//          Example:

//          text = JSON.stringify(["e", {pluribus: "unum"}]);
//          // text is '["e",{"pluribus":"unum"}]'

//          text = JSON.stringify(["e", {pluribus: "unum"}], null, "\t");
//          // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'

//          text = JSON.stringify([new Date()], function (key, value) {
//              return this[key] instanceof Date
//                  ? "Date(" + this[key] + ")"
//                  : value;
//          });
//          // text is '["Date(---current time---)"]'

//      JSON.parse(text, reviver)
//          This method parses a JSON text to produce an object or array.
//          It can throw a SyntaxError exception.

//          The optional reviver parameter is a function that can filter and
//          transform the results. It receives each of the keys and values,
//          and its return value is used instead of the original value.
//          If it returns what it received, then the structure is not modified.
//          If it returns undefined then the member is deleted.

//          Example:

//          // Parse the text. Values that look like ISO date strings will
//          // be converted to Date objects.

//          myData = JSON.parse(text, function (key, value) {
//              var a;
//              if (typeof value === "string") {
//                  a =
//   /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
//                  if (a) {
//                      return new Date(Date.UTC(
//                         +a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]
//                      ));
//                  }
//                  return value;
//              }
//          });

//          myData = JSON.parse(
//              "[\"Date(09/09/2001)\"]",
//              function (key, value) {
//                  var d;
//                  if (
//                      typeof value === "string"
//                      && value.slice(0, 5) === "Date("
//                      && value.slice(-1) === ")"
//                  ) {
//                      d = new Date(value.slice(5, -1));
//                      if (d) {
//                          return d;
//                      }
//                  }
//                  return value;
//              }
//          );

//  This is a reference implementation. You are free to copy, modify, or
//  redistribute.

/*jslint
    eval, for, this
*/

/*property
    JSON, apply, call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
    lastIndex, length, parse, prototype, push, replace, slice, stringify,
    test, toJSON, toString, valueOf
*/


// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.

if (typeof JSON !== 'object') {
    // noinspection JSValidateTypes
    JSON = {};
}

(function basic() {
    'use strict';

    var rx_one = /^[\],:{}\s]*$/;
    var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
    var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
    var rx_four = /(?:^|:|,)(?:\s*\[)+/g;
    var rx_escapable = /[\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
    var rx_dangerous = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;

    function f(n) {
        // Format integers to have at least two digits.
        return (n < 10)
            ? '0' + n
            : n;
    }

    function this_value() {
        return this.valueOf();
    }

    if (typeof Date.prototype.toJSON !== 'function') {

        Date.prototype.toJSON = function () {

            return isFinite(this.valueOf())
                ? (
                    this.getUTCFullYear()
                    + '-'
                    + f(this.getUTCMonth() + 1)
                    + '-'
                    + f(this.getUTCDate())
                    + 'T'
                    + f(this.getUTCHours())
                    + ':'
                    + f(this.getUTCMinutes())
                    + ':'
                    + f(this.getUTCSeconds())
                    + 'Z'
                )
                : null;
        };

        Boolean.prototype.toJSON = this_value;
        Number.prototype.toJSON = this_value;
        String.prototype.toJSON = this_value;
    }

    var gap;
    var indent;
    var meta;
    var rep;

    function quote(string) {

        // If the string contains no control characters, no quote characters, and no
        // backslash characters, then we can safely slap some quotes around it.
        // Otherwise we must also replace the offending characters with safe escape
        // sequences.

        rx_escapable.lastIndex = 0;
        return rx_escapable.test(string)
            ? '"' + string.replace(rx_escapable, function (a) {
            var c = meta[a];
            return typeof c === 'string'
                ? c
                : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
        }) + '"'
            : '"' + string + '"';
    }

    function str(key, holder) {

        // Produce a string from holder[key].

        var i;          // The loop counter.
        var k;          // The member key.
        var v;          // The member value.
        var length;
        var mind = gap;
        var partial;
        var value = holder[key];

        // If the value has a toJSON method, call it to obtain a replacement value.

        if (
            value
            && typeof value === 'object'
            && typeof value.toJSON === 'function'
        ) {
            value = value.toJSON(key);
        }

        // If we were called with a replacer function, then call the replacer to
        // obtain a replacement value.

        if (typeof rep === 'function') {
            value = rep.call(holder, key, value);
        }

        // What happens next depends on the value's type.

        switch (typeof value) {
            case 'string':
                return quote(value);

            case 'number':

                // JSON numbers must be finite. Encode non-finite numbers as null.

                return (isFinite(value))
                    ? String(value)
                    : 'null';

            case 'boolean':

                // If the value is a boolean or null, convert it to a string. Note:
                // typeof null does not produce "null". The case is included here in
                // the remote chance that this gets fixed someday.

                return String(value);

            // If the type is "object", we might be dealing with an object or an array or
            // null.

            case 'object':

                // Due to a specification blunder in ECMAScript, typeof null is "object",
                // so watch out for that case.

                if (!value) {
                    return 'null';
                }

                // Make an array to hold the partial results of stringifying this object value.

                gap += indent;
                partial = [];

                // Is the value an array?

                if (Object.prototype.toString.apply(value) === '[object Array]') {

                    // The value is an array. Stringify every element. Use null as a placeholder
                    // for non-JSON values.

                    length = value.length;
                    for (i = 0; i < length; i += 1) {
                        partial[i] = str(i, value) || 'null';
                    }

                    // Join all of the elements together, separated with commas, and wrap them in
                    // brackets.

                    v = partial.length === 0
                        ? '[]'
                        : gap
                            ? (
                                '[\n'
                                + gap
                                + partial.join(',\n' + gap)
                                + '\n'
                                + mind
                                + ']'
                            )
                            : '[' + partial.join(',') + ']';
                    gap = mind;
                    return v;
                }

                // If the replacer is an array, use it to select the members to be stringified.

                if (rep && typeof rep === 'object') {
                    length = rep.length;
                    for (i = 0; i < length; i += 1) {
                        if (typeof rep[i] === 'string') {
                            k = rep[i];
                            v = str(k, value);
                            if (v) {
                                partial.push(quote(k) + (
                                    (gap)
                                        ? ': '
                                        : ':'
                                ) + v);
                            }
                        }
                    }
                } else {

                    // Otherwise, iterate through all of the keys in the object.

                    for (k in value) {
                        if (Object.prototype.hasOwnProperty.call(value, k)) {
                            v = str(k, value);
                            if (v) {
                                partial.push(quote(k) + (
                                    (gap)
                                        ? ': '
                                        : ':'
                                ) + v);
                            }
                        }
                    }
                }

                // Join all of the member texts together, separated with commas,
                // and wrap them in braces.

                v = partial.length === 0
                    ? '{}'
                    : gap
                        ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
                        : '{' + partial.join(',') + '}';
                gap = mind;
                return v;
        }
    }

    // If the JSON object does not yet have a stringify method, give it one.

    if (typeof JSON.stringify !== 'function') {
        meta = {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"': '\\"',
            '\\': '\\\\',
        };

        /**
         * The JSON.stringify() method converts a JavaScript object or value to a JSON string,
         * optionally replacing values if a replacer function is specified or optionally
         * including only the specified properties if a replacer array is specified.
         * @param {any} value -
         * The value to convert to a JSON string.
         * @param {(this: any, key: string, value: any) => any} [replacer] -
         * A function that alters the behavior of the stringification process,
         * or an array of String and Number that serve as an allowlist
         * for selecting/filtering the properties of the value object to be included in the JSON string.
         * If this value is null or not provided,
         * all properties of the object are included in the resulting JSON string.
         * @param {string | number} [space] -
         * A String or Number object that's used to insert white space
         * into the output JSON string for readability purposes.
         * If this is a Number, it indicates the number of space characters
         * to use as white space for indenting purposes; this number is capped
         * at 10 (if it is greater, the value is just 10).
         * Values less than 1 indicate that no space should be used.
         * If this is a String, the string (or the first 10 characters of the string,
         * if it's longer than that) is used as white space.
         * If this parameter is not provided (or is null), no white space is used.
         * @return {string} -
         * A JSON string representing the given value, or undefined.
         */
        JSON.stringify = function (value, replacer, space) {

            // The stringify method takes a value and an optional replacer, and an optional
            // space parameter, and returns a JSON text. The replacer can be a function
            // that can replace values, or an array of strings that will select the keys.
            // A default replacer method can be provided. Use of the space parameter can
            // produce text that is more easily readable.

            var i;
            gap = '';
            indent = '';

            // If the space parameter is a number, make an indent string containing that
            // many spaces.

            if (typeof space === 'number') {
                for (i = 0; i < space; i += 1) {
                    indent += ' ';
                }

                // If the space parameter is a string, it will be used as the indent string.

            } else if (typeof space === 'string') {
                indent = space;
            }

            // If there is a replacer, it must be a function or an array.
            // Otherwise, throw an error.

            rep = replacer;
            if (replacer && typeof replacer !== 'function' && (
                typeof replacer !== 'object'
                || typeof replacer.length !== 'number'
            )) {
                throw new Error('JSON.stringify');
            }

            // Make a fake root object containing our value under the key of "".
            // Return the result of stringifying the value.

            return str('', {'': value});
        };
    }

    // If the JSON object does not yet have a parse method, give it one.

    if (typeof JSON.parse !== 'function') {
        /**
         * The JSON.parse() method parses a JSON string,
         * constructing the JavaScript value or object described by the string.
         * An optional reviver function can be provided to perform
         * a transformation on the resulting object before it is returned.
         * @param {string} text -
         * The string to parse as JSON. See the JSON object for a description of JSON syntax.
         * @param {(this: any, key: string, value: any) => any} reviver -
         * If a function, this prescribes how the value
         * originally produced by parsing is transformed, before being returned.
         * @return {any} -
         * The Object, Array, string, number, boolean, or null value corresponding to the given JSON text.
         */
        JSON.parse = function (text, reviver) {

            // The parse method takes a text and an optional reviver function, and returns
            // a JavaScript value if the text is a valid JSON text.

            var j;

            function walk(holder, key) {

                // The walk method is used to recursively walk the resulting structure so
                // that modifications can be made.

                var k;
                var v;
                var value = holder[key];
                if (value && typeof value === 'object') {
                    for (k in value) {
                        if (Object.prototype.hasOwnProperty.call(value, k)) {
                            v = walk(value, k);
                            if (v !== undefined) {
                                value[k] = v;
                            } else {
                                delete value[k];
                            }
                        }
                    }
                }
                return reviver.call(holder, key, value);
            }

            // Parsing happens in four stages. In the first stage, we replace certain
            // Unicode characters with escape sequences. JavaScript handles many characters
            // incorrectly, either silently deleting them, or treating them as line endings.

            text = String(text);
            rx_dangerous.lastIndex = 0;
            if (rx_dangerous.test(text)) {
                text = text.replace(rx_dangerous, function (a) {
                    return (
                        '\\u'
                        + ('0000' + a.charCodeAt(0).toString(16)).slice(-4)
                    );
                });
            }

            // In the second stage, we run the text against regular expressions that look
            // for non-JSON patterns. We are especially concerned with "()" and "new"
            // because they can cause invocation, and "=" because it can cause mutation.
            // But just to be safe, we want to reject all unexpected forms.

            // We split the second stage into 4 regexp operations in order to work around
            // crippling inefficiencies in IE's and Safari's regexp engines. First we
            // replace the JSON backslash pairs with "@" (a non-JSON character). Second, we
            // replace all simple value tokens with "]" characters. Third, we delete all
            // open brackets that follow a colon or comma or that begin the text. Finally,
            // we look to see that the remaining characters are only whitespace or "]" or
            // "," or ":" or "{" or "}". If that is so, then the text is safe for eval.

            if (
                rx_one.test(
                    text
                        .replace(rx_two, '@')
                        .replace(rx_three, ']')
                        .replace(rx_four, ''),
                )
            ) {

                // In the third stage we use the eval function to compile the text into a
                // JavaScript structure. The "{" operator is subject to a syntactic ambiguity
                // in JavaScript: it can begin a block or an object literal. We wrap the text
                // in parens to eliminate the ambiguity.

                j = eval('(' + text + ')');

                // In the optional fourth stage, we recursively walk the new structure, passing
                // each name/value pair to a reviver function for possible transformation.

                return (typeof reviver === 'function')
                    ? walk({'': j}, '')
                    : j;
            }

            // If the text is not JSON parseable, then a SyntaxError is thrown.

            throw new SyntaxError('JSON.parse');
        };
    }
}());

(function cycle() {
    /*
        cycle.js
        2021-05-31

        Public Domain.

        NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.

        This code should be minified before deployment.
        See https://www.crockford.com/jsmin.html

        USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
        NOT CONTROL.
    */

    // The file uses the WeakMap feature of ES6.

    /*jslint eval */

    /*property
        $ref, decycle, forEach, get, indexOf, isArray, keys, length, push,
        retrocycle, set, stringify, test
    */

    if (typeof JSON.decycle !== 'function') {
        /**
         * Make a deep copy of an object or array, assuring that there is at most one instance
         * of each object or array in the resulting structure.
         * The duplicate references (which might be forming cycles) are replaced
         * with an object of the form {"$ref": PATH} where the PATH is
         * a JSONPath string that locates the first occurance.
         *
         * If a replacer function is provided, then it will be called for each value.
         * A replacer function receives a value and returns a replacement value.
         *
         * JSONPath is used to locate the unique object.
         * $ indicates the top level of the object or array.
         * [NUMBER] or [STRING] indicates a child element or property.
         * @example
         * var a = [];
         * a[0] = a;
         * // produces the string '[{"$ref":"$"}]'.
         * return JSON.stringify(JSON.decycle(a));
         * @example
         * let o = {};
         * o.a = o;
         *
         * let p = (JSON.retrocycle(JSON.parse(JSON.stringify(JSON.decycle(o)))));
         * console.log(p.a.a.a === p.a); // true
         */
        JSON.decycle = function decycle(object, replacer) {
            'use strict';

            var objects = new WeakMap();     // object to path mappings

            return (function derez(value, path) {

                // The derez function recurses through the object, producing the deep copy.

                var old_path;   // The path of an earlier occurance of value
                var nu;         // The new object or array

                // If a replacer function was provided, then call it to get a replacement value.

                if (replacer !== undefined) {
                    value = replacer(value);
                }

                // typeof null === "object", so go on if this value is really an object but not
                // one of the weird builtin objects.

                if (
                    typeof value === 'object'
                    && value !== null
                    && !(value instanceof Boolean)
                    && !(value instanceof Date)
                    && !(value instanceof Number)
                    && !(value instanceof RegExp)
                    && !(value instanceof String)
                ) {

                    // If the value is an object or array, look to see if we have already
                    // encountered it. If so, return a {"$ref":PATH} object. This uses an
                    // ES6 WeakMap.

                    old_path = objects.get(value);
                    if (old_path !== undefined) {
                        return {$ref: old_path};
                    }

                    // Otherwise, accumulate the unique value and its path.

                    objects.set(value, path);

                    // If it is an array, replicate the array.

                    if (Array.isArray(value)) {
                        nu = [];
                        value.forEach(function (element, i) {
                            nu[i] = derez(element, path + '[' + i + ']');
                        });
                    } else {

                        // If it is an object, replicate the object.

                        nu = {};
                        Object.keys(value).forEach(function (name) {
                            nu[name] = derez(
                                value[name],
                                path + '[' + JSON.stringify(name) + ']',
                            );
                        });
                    }
                    return nu;
                }
                return value;
            }(object, '$'));
        };
    }

    if (typeof JSON.retrocycle !== 'function') {
        /**
         * Restore an object that was reduced by decycle.
         * Members whose values are objects of the form {$ref: PATH} are replaced with references to the value found by the PATH.
         * This will restore cycles.
         * The object will be mutated.
         *
         * The eval function is used to locate the values described by a PATH.
         * The root object is kept in a $ variable.
         * A regular expression is used to assure that the PATH is extremely well formed.
         * The regexp contains nested quantifiers.
         * That has been known to have extremely bad performance problems on some browsers for very long strings.
         * A PATH is expected to be reasonably short.
         * A PATH is allowed to belong to a very restricted subset of Goessner's JSONPath.
         * @example
         * var s = '[{"$ref":"$"}]';
         * // produces an array containing a single element which is the array itself.
         * return JSON.retrocycle(JSON.parse(s));
         * @example
         * let o = {};
         * o.a = o;
         *
         * let p = (JSON.retrocycle(JSON.parse(JSON.stringify(JSON.decycle(o)))));
         * console.log(p.a.a.a === p.a); // true
         */
        JSON.retrocycle = function retrocycle($) {
            'use strict';

            var px = /^\$(?:\[(?:\d+|"(?:[^\\"\u0000-\u001f]|\\(?:[\\"\/bfnrt]|u[0-9a-zA-Z]{4}))*")])*$/;

            (function rez(value) {

                // The rez function walks recursively through the object looking for $ref
                // properties. When it finds one that has a value that is a path, then it
                // replaces the $ref object with a reference to the value that is found by
                // the path.

                if (value && typeof value === 'object') {
                    if (Array.isArray(value)) {
                        value.forEach(function (element, i) {
                            if (typeof element === 'object' && element !== null) {
                                var path = element['$ref'];
                                if (typeof path === 'string' && px.test(path)) {
                                    value[i] = eval(path);
                                } else {
                                    rez(element);
                                }
                            }
                        });
                    } else {
                        Object.keys(value).forEach(function (name) {
                            var item = value[name];
                            if (typeof item === 'object' && item !== null) {
                                var path = item['$ref'];
                                if (typeof path === 'string' && px.test(path)) {
                                    value[name] = eval(path);
                                } else {
                                    rez(item);
                                }
                            }
                        });
                    }
                }
            }($));

            return $;
        };
    }
})();

module.exports = {
    stringify: JSON.stringify,
    parse: JSON.parse,
    decycle: JSON.decycle,
    retrocycle: JSON.retrocycle,
};